package com.xydpeng.rpc.protocol;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author xydpeng
 * @version v1.0
 * @date: 2023/4/10
 * @description:
 */
public class HandlerInvokerClassCodeGenerator {

    public final Class<?> type = HandlerInvoker.class;

    private static final String CODE_PACKAGE = "package %s;\n";

    private static final String CODE_IMPORTS = "import %s;\n";

    private static final String CODE_CLASS_DECLARATION = "public class %s$HandlerInvoker implements %s {\n";

    private static final String CODE_FIELDS = "private %s %s = (%s) HandlerMapping.getServiceConfig(%d).getRef();\n";

    private static final String CODE_INVOKE_METHOD = "public Object invoke(int handlerId, Object[] params) {\n";

    private static final String CODE_CASE = "case %d :\n";

    private static final String CODE_CALLER = "this.%s.%s(%s);\n";

    private static final String CODE_BREAK = "break;\n";


    public String generate(Class interfaceClass, int index) {
        StringBuilder code = new StringBuilder();
        code.append(generatePackageInfo());
        code.append(generateImports(interfaceClass));
        code.append(generateClassDeclaration(interfaceClass));
        code.append(generateFields(interfaceClass, index));

        code.append(generateInvokeMethod(interfaceClass));

        code.append("}");
        return code.toString();
    }

    private String generatePackageInfo() {
        return String.format(CODE_PACKAGE, type.getPackage().getName());
    }

    private String generateImports(Class interfaceClass) {
        StringBuilder builder = new StringBuilder();
        ServiceMeta serviceMeta = HandlerMapping.getServiceMetaMap().get(interfaceClass);
        Map<Integer, HandlerMeta> handlerMetaMap = serviceMeta.getHandlerMetaMap();
        Set<Class> importClzSet = new HashSet<>();
        importClzSet.add(HandlerMapping.class);
        importClzSet.add(interfaceClass);
        importClzSet.add(serviceMeta.getRef().getClass());
        handlerMetaMap.values().stream().forEach(meta -> {
            importClzSet.addAll(Arrays.asList(meta.getParameterTypes()));
            importClzSet.add(meta.getReturnType());
        });
        for (Class clz : importClzSet) {
            if (isBasicType(clz)) {
                continue;
            }
            builder.append(String.format(CODE_IMPORTS, clz.getCanonicalName()));

        }
        return builder.toString();
    }


    private String generateClassDeclaration(Class interfaceClass) {
        return String.format(CODE_CLASS_DECLARATION, interfaceClass.getSimpleName(), type.getCanonicalName());
    }

    private String generateFields(Class interfaceClass, int index) {
        StringBuilder builder = new StringBuilder();
        builder.append(String.format(CODE_FIELDS, interfaceClass.getSimpleName(), "ref" + interfaceClass.getSimpleName(), interfaceClass.getSimpleName(), index));
        return builder.toString();
    }

    private String generateInvokeMethod(Class interfaceClass) {
        StringBuilder builder = new StringBuilder();
        ServiceMeta serviceMeta = HandlerMapping.getServiceMetaMap().get(interfaceClass);
        Map<Integer, HandlerMeta> handlerMetaMap = serviceMeta.getHandlerMetaMap();
        Set<Integer> methodIds = handlerMetaMap.keySet();
        builder.append(CODE_INVOKE_METHOD);
        builder.append("Object result = null;\n");
        builder.append(" switch (handlerId) {\n");
        for (Integer methodId : methodIds) {
            HandlerMeta methodMeta = handlerMetaMap.get(methodId);
            Class[] parameterTypes = methodMeta.getParameterTypes();
            Class returnType = methodMeta.getReturnType();
            builder.append(String.format(CODE_CASE, methodId.intValue()));
            if (!void.class.equals(returnType) && !Void.class.equals(returnType)) {
                builder.append(" result = ");
            }
            String paramStr = "";
            if (parameterTypes != null && parameterTypes.length > 0) {
                StringBuilder paramBuilder = new StringBuilder();
                for (int i = 0; i < parameterTypes.length; i++) {
                    paramBuilder.append("(").append(parameterTypes[i].getSimpleName()).append(")").append("params[").append(i).append("]");
                    if (i != parameterTypes.length - 1) {
                        paramBuilder.append(",");
                    }
                }
                paramStr = paramBuilder.toString();
            }
            builder.append(String.format(CODE_CALLER, "ref" + interfaceClass.getSimpleName(), methodMeta.getMethod().getName(), paramStr));
            builder.append(CODE_BREAK);
        }
        builder.append("}\n");
        builder.append("return result;\n");
        builder.append("}\n");
        return builder.toString();
    }

    private boolean isBasicType(Class clazz) {
        return int.class.equals(clazz) ||
                long.class.equals(clazz) ||
                float.class.equals(clazz) ||
                double.class.equals(clazz) ||
                short.class.equals(clazz) ||
                char.class.equals(clazz) ||
                byte.class.equals(clazz) ||
                boolean.class.equals(clazz) ||
                void.class.equals(clazz);
    }

    public static void main(String[] args) {
        HandlerInvokerClassCodeGenerator generator = new HandlerInvokerClassCodeGenerator();
        System.out.println(generator.type.getName());
        System.out.println(generator.type.getCanonicalName());
        Method[] methods = generator.type.getDeclaredMethods();
        System.out.println(generator.type.getDeclaredMethods()[0].getReturnType());
    }


}
